/**
 * Digest Authentication Module
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "http_status.h"
#include "authn.h"
#include "digest_auth.h"
#include "nweb_log.h"
#include "md5.h"

/* Client Digest Response struct */
typedef struct digest_header_struct {
    //const char           *scheme;
    const char           *realm;
    const char           *username;
          char           *nonce;
    const char           *uri;
    //const char           *method;
    const char           *digest;
    //const char           *algorithm;
    const char           *cnonce;
    const char           *opaque;
    //unsigned long         opaque_num;
    const char           *message_qop;
    const char           *nonce_count;
} digest_header_rec;

static void note_auth_failure(request_rec *r);
static int get_digest_rec(request_rec *r, digest_header_rec *resp);
static char *get_session_HA1(request_rec *r);
static char *get_session_HA2(digest_header_rec *resp);
static char *get_digest(request_rec *r, digest_header_rec *resp);

int authenticate_digest_user(request_rec *r) {
    digest_header_rec *resp = (digest_header_rec*) malloc(sizeof(*resp));
    
    // fill out the digest config
    if (get_digest_rec(r, resp) == HTTP_UNAUTHORIZED) {
	note_auth_failure(r);
	return HTTP_UNAUTHORIZED;
    }
    
    const char *server_digest = get_digest(r, resp);
    const char *client_digest = resp->digest;
    
    // if (server digest == client digest)
    if (strcmp(server_digest, client_digest) == 0) {
	return OK;
    }
    else {
	note_auth_failure(r);
	return HTTP_UNAUTHORIZED;
    }
}

static void note_auth_failure(request_rec *r) {
    // Hard code server response
    char* realm		= "testrealm@host.com";
    char* qop		= "auth,auth-int";
    char* nonce		= "dcd98b7102dd2f0e8b11d0f600bfb0c093"; // TODO: gen_nonce()
    char* opaque	= "5ccc069c403ebaf9f0171e9517f40e41"; // TODO: gen_opaque()
    
    (void) sprintf(r->buffer, "HTTP/1.0 401 Authorization Required\r\nWWW-Authenticate: Digest realm=\"%s\",qop=\"%s\",nonce=\"%s\",opaque=\"%s\"\r\n\r\n",
	    realm, qop, nonce, opaque);
    (void) write(r->fd, r->buffer, strlen(r->buffer));
    (void) sprintf(r->buffer, "<html><head><title>Auth Error</title></head><body><h1>401 Unauthorized.</h1></body></html>\r\n\r\n");
    (void) write(r->fd, r->buffer, strlen(r->buffer));
}

static int get_digest_rec(request_rec *r, digest_header_rec *resp) {
    char* request = r->buffer;
    
    char delimiters[] = "\n\r\", =";
    char* token = strtok(request, delimiters);
    while(token != NULL) {
	if(strcmp(token, "realm") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->realm = token;
	}
	else if(strcmp(token, "username") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->username = token;
	}
	else if(strcmp(token, "nonce") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->nonce = token;
	}
	else if(strcmp(token, "uri") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->uri = token;
	}
	else if(strcmp(token, "cnonce") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->cnonce = token;
	}
	else if(strcmp(token, "opaque") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->opaque = token;
	}
	else if(strcmp(token, "qop") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->message_qop = token;
	}
	else if(strcmp(token, "nc") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->nonce_count = token;
	}
	else if(strcmp(token, "response") == 0) {
	    token = strtok(NULL, delimiters);
	    resp->digest = token;
	}
	token = strtok(NULL, delimiters);
    }
    
    if (!resp->username || !resp->realm || !resp->nonce || !resp->uri
        || !resp->digest
        || (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) {
        return HTTP_UNAUTHORIZED;
    }
    else {
	return OK;
    }
}

static char *get_session_HA1(request_rec *r) {
    char *user = malloc(256);
    char *realm = malloc(256);
    char *a1 = malloc(256);

    // Read local password file
    auth_config *config = get_auth_config(r);
    
    FILE *ifp = fopen(config->auth_user_file, "r");
    if (ifp) {
	fscanf(ifp, "%256[^:\n]:%256[^:\n]:%256[^:\n]", user, realm, a1);
    }
    
    return a1;
}

static char *get_session_HA2(digest_header_rec *resp) {
    char* digest_uri = (char*)malloc(BUFSIZE);
    strcat(digest_uri, "GET:");
    strcat(digest_uri, resp->uri);
    return md5(digest_uri);
}

static char *get_digest(request_rec *r, digest_header_rec *resp) {
    char* digest = (char*)malloc(BUFSIZE);
    
    char* ha1 = get_session_HA1(r);
    char* ha2 = get_session_HA2(resp);
    
    // Digest Structure
    // HA1 result, 
    // server nonce (nonce), 
    // request counter (nc), 
    // client nonce (cnonce), 
    // quality of protection code (qop),
    // HA2 result
    strcat(digest, ha1);
    strcat(digest, ":");
    strcat(digest, resp->nonce);
    strcat(digest, ":");
    strcat(digest, resp->nonce_count);
    strcat(digest, ":");
    strcat(digest, resp->cnonce);
    strcat(digest, ":");
    strcat(digest, resp->message_qop);
    strcat(digest, ":");
    strcat(digest, ha2);
    
    return md5(digest);
}